<?php
/**
 * Created by PhpStorm.
 * User: gabriel
 * Date: 18-11-12
 * Time: 下午3:31
 * 微信支付
 */
namespace Common;

class Wechat{
  private $app_id;
  private $mch_id;
  private $key;
  private $notify_url;

  private $err_code;
  private $err_msg;

  public function __construct() {
    $this->app_id = \WechatConfig::APPID;
    $this->mch_id = \WechatConfig::MCHID;
    $this->key = \WechatConfig::KEY;
    $this->notify_url = \WechatConfig::NOTIFY_URL;
  }

  /**
   * 统一下单
   * @param string $body 商品描述交易字段格式根据不同的应用场景按照以下格式：
   * APP——需传入应用市场上的APP名字-实际商品名称，天天爱消除-游戏充值。
   * @param int $total_fee 订单总金额，单位为分
   * @param string $fee_type 货币类型 默认人民币：CNY
   * @param string $device_info 终端设备号(门店号或收银设备ID)，默认请传"WEB"
   * @param string $detail 商品详细描述
   * @param string $attach 附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据
   * @param string $sign_type 签名类型，目前支持HMAC-SHA256和MD5，默认为MD5
   * @param string $trade_type 支付类型：如APP
   * @param string $limit_pay 指定支付方式 no_credit--指定不能使用信用卡支付
   * @param string $scene_info 该字段用于统一下单时上报场景信息，目前支持上报实际门店信息。
      {
      "store_id": "", //门店唯一标识，选填，String(32)
      "store_name":"”//门店名称，选填，String(64)
      }
   * @return array / null
   */
  public function unifiedorder(string $body,int $total_fee,string $fee_type = "CNY",
                               string $device_info = "WEB",string $detail = "",
                               string $attach = "",string $sign_type = "MD5",
                               string $trade_type = "APP",string $limit_pay = "",
                               string $scene_info = ""):?array {
    $data = [
      "appid" => $this->app_id,
      "mch_id" => $this->mch_id,
      "device_info" => $device_info,
      "nonce_str" => $this->getRandStr(),
      "sign_type" => $sign_type,
      "body" => $body,
      "detail" => $detail,
      "attach" => $attach,
      "out_trade_no" => $this->getOrderNumber(),
      "fee_type" => $fee_type,
      "total_fee" => $total_fee,
      "spbill_create_ip" => $this->getClientIp(),
      "time_start" => $this->getOrderTimeStart(),
      "time_expire" => $this->getOrderTimeExpire(),
      "goods_tag" => "",
      "notify_url" => $this->notify_url,
      "trade_type" => $trade_type,
      "limit_pay" => $limit_pay,
      "scene_info" => $scene_info
    ];
    $data["sign"] = $this->getSign($data,$sign_type);
    try{
      $curl = new CURL();
      $res = $curl->post(\WechatConfig::UNIFIEDORDERURL,$this->arrayToXml($data),[]);
      $result = $this->xmlToArray($res);
      Logger::getInstance()->info("result ".json_encode($result));
      if ($result['return_code'] == "FAIL"){
        $this->err_code = $result['return_code'];
        $this->err_msg = $result['return_msg'];
        return null;
      }
      if ($result['result_code'] == "FAIL"){
        $this->err_code = $result['err_code'];
        $this->err_msg = $result['err_code_des'];
        return null;
      }
      if ($result['sign'] != $data['sign']){
        $this->err_code = 415;
        $this->err_msg = "sign is error";
        return null;
      }
      return $result;
    }catch (\Exception $e){
      $this->err_code = $e->getCode();
      $this->err_msg = $e->getMessage();
      Logger::getInstance()->info("unifiedorder error ".$this->getErrMsg());
      return null;
    }
  }

  /**
   * 查询订单
   * @param string $transaction_id 微信订单号
   * @param string $out_trade_no 商户订单号
   * @param string $sign_type 签名类型
   * @return WechatInfo|null
   */
  public function orderquery(string $transaction_id,string $out_trade_no,
                             string $sign_type = "MD5"):?WechatInfo{
    $data = [
      "appid" => $this->app_id,
      "mch_id" => $this->mch_id,
      "transaction_id" => $transaction_id,
      "out_trade_no" => $out_trade_no,
      "nonce_str" => $this->getRandStr(),
      "sign_type" => $sign_type
    ];
    $data['sign'] = $this->getSign($data,$sign_type);
    try{
      $curl = new CURL();
      $res = $curl->post(\WechatConfig::ORDERQUERYURL,$this->arrayToXml($data),[]);
      /**
       * @var $result WechatInfo
       */
      $result = json_decode(json_encode($this->xmlToArray($res)));
      if ($result->return_code == "FAIL"){
        $this->err_code = $result->return_code;
        $this->err_msg = $result->return_msg;
        return null;
      }
      if ($result->result_code == "FAIL"){
        $this->err_code = $result->err_code;
        $this->err_msg = $result->err_code_des;
        return null;
      }
      if ($result->sign != $data['sign']){
        $this->err_code = 415;
        $this->err_msg = "sign is error";
        return null;
      }
      return $result;
    }catch (\Exception $exception){
      $this->err_code = $exception->getCode();
      $this->err_msg = $exception->getMessage();
      return null;
    }
  }

  /**
   * 关闭订单
   * @param string $out_trade_no
   * @param string $sign_type
   * @return bool
   */
  public function closeorder(string $out_trade_no,string $sign_type = "MD5"):bool {
    $data = [
      "appid" => $this->app_id,
      "mch_id" => $this->mch_id,
      "out_trade_no" => "",
      "nonce_str" => $this->getRandStr(),
      "sign_type" => $sign_type
    ];
    $data['sign'] = $this->getSign($data,$sign_type);
    try{
      $curl = new CURL();
      $res = $curl->post(\WechatConfig::CLOSEORDERURL,$this->arrayToXml($data),[]);
      $result = $this->xmlToArray($res);
      if ($result['return_code'] == "FAIL"){
        $this->err_code = $result['return_code'];
        $this->err_msg = $result['return_msg'];
        return false;
      }
      if ($result['result_code'] = "FAIL"){
        $this->err_code = $result['err_code'];
        $this->err_msg = $result['err_code_des'];
        return false;
      }
      if ($result['sign'] != $data['sign']){
        $this->err_code = 415;
        $this->err_msg = "sign is error";
        return false;
      }
      return true;
    }catch (\Exception $exception){
      $this->err_code = $exception->getCode();
      $this->err_msg = $exception->getMessage();
      return false;
    }
  }


  /**
   * 生成32位的随机字符串
   * @return string
   */
  private function getRandStr():string {
    return md5(time().mt_rand(0,10000));
  }

  /**
   * 生成签名sign
   * @param array &$data
   * @param string $sign_type 签名方式 目前支持MD5和HMAC-SHA256
   * @return string
   */
  private function getSign(array &$data,string $sign_type = "MD5"):string {
    //按参数字典序排序
    ksort($data);
    foreach ($data as $key => $value){
      if (empty($value)) unset($data[$key]);
    }
    $stringA = http_build_query($data);
    $stringSignTemp = $stringA.$this->key;
    if ($sign_type == "MD5"){
      $sign = strtoupper(md5($stringSignTemp));
    }else{
      $sign = strtoupper(hash_hmac("sha256",$stringSignTemp,$this->key));
    }
    return $sign;
  }

  /**
   * 生成商户的订单号
   * @return string
   */
  private function getOrderNumber():string {
    return md5(time().mt_rand(1,999)."-Followup".mt_rand(11,99999));
  }

  /**
   * 获取用户端的IP地址
   * @return string
   */
  private function getClientIp():string {
    return $_SERVER['REMOTE_ADDR'];
  }

  /**
   * 获取交易开始时间即订单开始时间
   * @return string
   */
  private function getOrderTimeStart():string {
    return date("YmdHis",time());
  }

  /**
   * 获取交易结束时间即订单失效时间
   * @return string
   */
  private function getOrderTimeExpire():string {
    return date("YmdHis",time());
  }

  /**
   * 将数组元素转为xml格式的数据
   * @param array $arr
   * @return string
   */
  private function arrayToXml(array $arr){
    $xml = "<xml>";
    foreach ($arr as $key=>$val){
      if(is_array($val)){
        $xml.="<".$key.">".$this->arrayToXml($val)."</".$key.">";
      }else{
        $xml.="<".$key.">".$val."</".$key.">";
      }
    }
    $xml.="</xml>";
    return $xml;
  }

  /**
   * 将xml格式转为数组元素
   * @param string $xml
   * @return array
   */
  private function xmlToArray(string $xml):array {
    return json_decode(json_encode(simplexml_load_string($xml,
      'SimpleXMLElement', LIBXML_NOCDATA)),true);
  }

  public function getErrCode(){
    return $this->err_code;
  }

  public function getErrMsg(){
    return $this->err_msg;
  }

}